package cn.zzq.aix.nativeinterface.javareflector;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

import com.google.appinventor.components.runtime.collect.Sets;
import com.google.appinventor.components.runtime.util.YailDictionary;
import com.google.appinventor.components.runtime.util.YailProcedure;


public class ReflectUtil {
	public interface ConstructorFilter {
		public boolean accept(Constructor<?> constructor);
	}

	public interface MethodFilter {
		public boolean accept(Method method);
	}
	public interface FieldFilter{
		public boolean accept(Field field);
	}

	public interface ExceptionCallback {
		public void catchException(Throwable exception);
	}



	public static Object invokeMethod(Object instance, Method method, Object[] args) throws IllegalAccessException {
		return invokeMethod(instance, method, args, null);
	}

	public static Object invokeMethod(Object instance, Method method, Object[] args,
			ExceptionCallback exceptionCallback) throws IllegalAccessException {
		method.setAccessible(true);
		try {
			if (args == null) {
				return method.invoke(instance);
			} else {
				return method.invoke(instance, args);
			}

		} catch (InvocationTargetException e) {
			if (exceptionCallback != null) {
				exceptionCallback.catchException( e.getTargetException());
			}else {
				throw new ReflectException(new Object[] {instance,method,args},"ReflectUtil.invokeMethod<Object,Method,Object[]>","执行的方法抛出了一个异常，请使用ExceptionCallback来回调捕获异常");
			}
		} catch (IllegalAccessException e) {
			throw e;
		}
		return null;
	}

	public static Field getField(Class<?> class_, String name) throws NoSuchFieldException {
		try {
			return class_.getField(name);
		} catch (NoSuchFieldException e) {
			try {
				return class_.getDeclaredField(name);
			} catch (NoSuchFieldException e1) {
				e1.getStackTrace();
				throw e1;
			}
		}

	}

	public static Set<String> getFieldNames(Class<?> class_) {
		Set<String> fieldNames = Sets.newHashSet();
		for (Field field : getFields(class_)) {
			fieldNames.add(field.getName());
		}
		return fieldNames;
	}

	public static Set<Field> getFields(Class<?> class_) {
		Set<Field> allFields = Sets.newHashSet();
		addArrayToCollection(class_.getFields(), allFields);
		addArrayToCollection(class_.getDeclaredFields(), allFields);
		return allFields;
	}

	public static Set<Field> getFilteredField(Collection<Field> fields,FieldFilter filter){
		Set<Field> filiteredFields = Sets.newHashSet();
		for (Field field : fields) {
			if (filter.accept(field)) {
				filiteredFields.add(field);
			}
		}
		return filiteredFields;
	}
	
	public static YailDictionary getConstantMap(Class<?> class_){
		final YailDictionary constantDict=YailDictionary.makeDictionary();
		getFilteredField(getFields(class_), new FieldFilter() {
			@Override
			public boolean accept(Field field) {
				int modifiers=field.getModifiers();
				boolean isPublic=Modifier.isPublic(modifiers);
				boolean isStatic=Modifier.isStatic(modifiers);
				boolean isFinal=Modifier.isFinal(modifiers);
				return isPublic&&isStatic&&isFinal;
			}
		}).forEach(new Consumer<Field>() {
			@Override
			public void accept(Field filteredField) {
				try {
					constantDict.put(filteredField.getName(), filteredField.get(null));
				} catch (Exception e) {
					e.printStackTrace();
				} 
			}
			
		});
		return constantDict;
	}

	
	public static Method getMethod(Class<?> class_, String name, Class<?>... parameterTypes)
			throws NoSuchMethodException {
		try {
			return class_.getMethod(name, parameterTypes);
		} catch (NoSuchMethodException e) {
			try {
				return class_.getDeclaredMethod(name, parameterTypes);
			} catch (NoSuchMethodException e1) {
				throw e1;
			}
		}
	}

	public static Set<Method> getFilteredMethods(Collection<Method> methods, MethodFilter filter) {
		Set<Method> filiteredMethods = Sets.newHashSet();
		for (Method method : methods) {
			if (filter.accept(method)) {
				filiteredMethods.add(method);
			}
		}
		return filiteredMethods;
	}

	public static Set<Method> getMethods(Class<?> class_) {
		Method[] declaredMethods = class_.getDeclaredMethods();
		Method[] methods = class_.getMethods();
		Set<Method> allMethods = Sets.newHashSet();
		addArrayToCollection(declaredMethods, allMethods);
		addArrayToCollection(methods, allMethods);
		return allMethods;
	}

	public static Set<Class<?>> getInnerClasses(Class<?> class_) {
		Set<Class<?>> allInnerClasses = Sets.newHashSet();
		addArrayToCollection(class_.getClasses(), allInnerClasses);
		addArrayToCollection(class_.getDeclaredClasses(), allInnerClasses);
		return allInnerClasses;
	}

	/**
	 * 使用函数字典创建接口
	 * 
	 * @param interfaceClasses
	 *            接口类对象或接口名
	 * @param methodDict
	 *            接口方法字典，{方法名1:匿名函数1,方法名1:匿名函数1, ... ,方法名n:,匿名函数n}
	 * @return 代理后的接口对象
	 */
	public static Object createInstanceFromInterface(final ClassLoader classLoader, final Class<?>[] interfaceClasses,
			final Map<Object, Object> functionMap) {
		return Proxy.newProxyInstance(classLoader, interfaceClasses, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws NoSuchMethodException   {
				String methodName = method.getName();
				YailProcedure function = (YailProcedure) functionMap.get(methodName);
				if (function != null) {
					if(args==null) {
						return function.call();
					}else {
						return function.call(args);
					}
				} else if (methodName.equals("hashCode")) {
					StringBuffer sBuffer = new StringBuffer();
					for (Object procedures : functionMap.values()) {
						sBuffer.append(procedures.hashCode());
					}
					return sBuffer.toString().hashCode();
				} else if (methodName.equals("toString")) {
					return "Proxy implements " + Arrays.toString(interfaceClasses);
				} else if (methodName.equals("equals")) {
					return proxy.hashCode() == args[0].hashCode();
				} else {
					throw new NoSuchMethodException("没有实现接口方法"+method);
				}
			}
		});
	}

	public static Object createInstanceByConstructor(Constructor<?> constructor, Object[] initargs) {
		return createInstanceByConstructor(constructor, initargs, null);
	}

	public static Object createInstanceByConstructor(Constructor<?> constructor, Object[] initargs,
			ExceptionCallback exceptionCallback) {
		try {
			if (initargs.length == 0 || initargs == null) {
				return constructor.newInstance();
			} else {
				return constructor.newInstance(initargs);
			}

		} catch (InvocationTargetException e) {
			if (exceptionCallback != null) {
				exceptionCallback.catchException((Exception) e.getTargetException());
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return null;
	}

	public static Constructor<?> getConstructor(Class<?> class_, Class<?>... parameterTypes)
			throws NoSuchMethodException {
		try {
			return class_.getConstructor(parameterTypes);
		} catch (NoSuchMethodException e) {
			try {
				return class_.getDeclaredConstructor(parameterTypes);
			} catch (NoSuchMethodException e1) {
				throw e1;
			}
		}
	}

	/**
	 * 获取表示传入的对象的类对象
	 * 
	 * @param object
	 *            需要传入的对象
	 * @return 表示该对象的类的对象
	 */
	public static Class<?> getClassByObject(Object object) {
		return object == null ? null : object.getClass();
	}

	/**
	 * 返回一个对象的字符串表示(将调用对象内部的toString方法)
	 * 
	 * @param object
	 *            一个对象
	 * @return 该对象的字符串表示
	 */
	public static String toString(Object object) {
		if (object == null) {
			return "null";
		}
		return object.toString();
	}

	/**
	 * 判断两个对象是否相等
	 * 
	 * @param object1
	 *            对象1
	 * @param object2
	 *            对象2
	 * @return 是否相等
	 */
	public static boolean isEqual(Object object1, Object object2) {
		if (object1 == null && object2 == null) {
			return true;
		} else if ((object1 == null && object2 != null) || (object1 != null && object2 == null)) {
			return false;
		}
		return object1.equals(object2);
	}


	
	public static Set<Constructor<?>> getFilteredConstructors(Collection<Constructor<?>> constructors,
			ConstructorFilter filter) {
		Set<Constructor<?>> filiteredConstructors = Sets.newHashSet();
		for (Constructor<?> constructor : constructors) {
			if (filter.accept(constructor)) {
				filiteredConstructors.add(constructor);
			}
		}
		return filiteredConstructors;
	}

	public static Set<Constructor<?>> getConstructors(Class<?> class_) {
		Constructor<?>[] declaredConstructors = class_.getDeclaredConstructors();
		Constructor<?>[] constructors = class_.getConstructors();
		Set<Constructor<?>> allConstructors = Sets.newHashSet();
		addArrayToCollection(declaredConstructors, allConstructors);
		addArrayToCollection(constructors, allConstructors);
		return allConstructors;
	}

	public static <T> Collection<T> addArrayToCollection(T[] array, Collection<T> collection) {
		if (collection == null) {
			collection = Sets.newHashSet();
		}
		for (int i = 0; i < array.length; i++) {
			collection.add(array[i]);
		}
		return collection;
	}

}
